package com.zxd.interview.workdayfind.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.zxd.interview.util.date.DateHelper;
import com.zxd.interview.workdayfind.bo.CalendarDataProcessBo;
import com.zxd.interview.workdayfind.dto.SaCalendarDayDto;
import com.zxd.interview.workdayfind.mapper.CalendarTableMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

import static com.zxd.interview.workdayfind.util.CheckWorkDayUtil.NEXT_TD_DAY_PATTERN;

/**
 * @author zhaoxudong
 * @version v1.0.0
 * @Package : com.zxd.interview.workdayfind
 * @Description : 工作日查找工具类
 * @Create on : 2021/12/16 16:30
 **/
@Slf4j
@Component
public class WorkDayFindUtil {

    @Autowired
    private CalendarTableMapper calendarTableMapper;

    /**
     * 用于 T日的“当天获取”
     */
    private static final String MATCE_NOW_DAY_LIST = "0";

    /**
     * 自然日
     */
    private static final String WORK_DAY_CONFIG_T = "T";
    /**
     * 工作日
     */
    private static final String WORK_DAY_CONFIG_D = "D";

    /**
     * 日历表不是工作日标识
     */
    private static final String IS_NOT_WORK_DAY = "N";

    /**
     * 工作日
     */
    private static final String IS_WORK_DAY = "Y";


    /**
     * t-D 天数的的正则表达式
     */
    private static final Pattern PREV_TD_DAY_PATTERN = Pattern.compile("^[TD]-(0|[1-9][0-9]*)$");


    /**
     * 获取下一个工作日，或者下一个自然日，工作日计算由计算非工作日天数累加而成
     *
     * @param calendarDataProcessBo
     * @return java.lang.String
     * @description 获取下一个工作日，或者下一个自然日，工作日计算由计算非工作日天数累加而成
     * @author 赵小东
     * @date 2022/6/16 18:21
     */
    public String findNextDayByCalendarList(CalendarDataProcessBo calendarDataProcessBo) throws IllegalAccessException {
        Objects.requireNonNull(calendarDataProcessBo);
        log.info("获取下一个工作日或者自然日入参：结算周期:{}, 需要额外推迟的工作日天数:{}", calendarDataProcessBo.getBankSettleCycle(), calendarDataProcessBo.getExtDayOfWorkDayCount());
//        if (!StrUtil.isAllNotBlank(calendarDataProcessBo.getBankSettleCycle())
//                || CollectionUtil.isEmpty(calendarDataProcessBo.getCalendarDayDtos())) {
//            throw new IllegalArgumentException("传递参数有误，请确保所有参数均已传递");
//        }
        if (log.isDebugEnabled()) {
            log.debug("当前获取下一个工作日或者自然日入参，结算周期 {}， 需要额外推迟的工作日天数 {}", calendarDataProcessBo.getBankSettleCycle(), calendarDataProcessBo.getExtDayOfWorkDayCount());
        }
        int extDayOfWorkDayCount = calendarDataProcessBo.getExtDayOfWorkDayCount();
        String bankSettleCycle = calendarDataProcessBo.getBankSettleCycle();
        List<SaCalendarDayDto> calendarDayDtos = calendarDataProcessBo.getCalendarDayDtos();
        // 校验是否为工作日或者自然日
        boolean matches = CheckWorkDayUtil.checkNextTdDayAccess(bankSettleCycle);
        if (!matches) {
            log.error("由于正则表达式{}不符合校验规则{}所以对账定时任务无法处理时间", bankSettleCycle, NEXT_TD_DAY_PATTERN);
            throw new UnsupportedOperationException(String.format("由于正则表达式%s不符合校验规则%s所以对账定时任务无法处理时间", bankSettleCycle, NEXT_TD_DAY_PATTERN));
        }
        String[] cycDay = bankSettleCycle.split("\\+");
        // 获取是工作日还是自然日计算
        String tOrDday = cycDay[0];
        // T+N或者D+N的N天
        String addDay = cycDay[1];
        boolean matchWorkDayEnable;
        if (Objects.equals(tOrDday, WORK_DAY_CONFIG_T)) {
            matchWorkDayEnable = true;
        } else if (Objects.equals(tOrDday, WORK_DAY_CONFIG_D)) {
            matchWorkDayEnable = false;
        } else {
            throw new UnsupportedOperationException("无法处理【T+N】或者【D+N】以外的数据");
        }
        // 如果天数为 0 ，则直接返回当天
        if(Objects.equals(MATCE_NOW_DAY_LIST, addDay)){
            String nowByNew = DateHelper.getNowByNew(DateHelper.yyyyMMdd);
            log.info("获取下一个工作日或者自然日出参：{}", nowByNew);
            return nowByNew;
        }
        int finDay = Integer.parseInt(addDay) + extDayOfWorkDayCount;
        // 如果不需要获取工作日，则直接按照t+N直接取n的数值，但是如果是工作日，则需要不断往后推一天获取到工作日
        // 如果是 D ，直接+N 取内容即可
        if (!matchWorkDayEnable) {
            Optional<SaCalendarDayDto> first = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), String.valueOf(finDay))).findFirst();
            if (!first.isPresent()) {
                throw new UnsupportedOperationException(String.format("calendarDayDtos 未发现 %s 自然日数据", bankSettleCycle));
            }
            return first.get().getCalendarDate();
        }
        // 如果是 T，需要一天天计算累积+够足够天数的工作日才能返回结果
        // 计算从 0 到 +N 中间有几个非工作日
        int countNotWorkDay = 0;
        // 循环累加到指定天数
        for (int currDay = 0; currDay <= finDay + 1; currDay++) {
            int finalCurrDay = currDay;
            Optional<SaCalendarDayDto> first = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), String.valueOf(finalCurrDay))).findFirst();
            if (!first.isPresent()) {
                String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 T+%s 日数据，请检查日历表数据是否正确", currDay);
                log.warn(logError);
                throw new RuntimeException(logError);
            }
            if (log.isDebugEnabled()) {
                log.debug("获取 T+{} 日数据，当前日期为：{}，是否为工作日：{}", currDay, first.get().getCalendarDate(), first.get().getIsWorkDay());
            }
            if (Objects.equals(first.get().getIsWorkDay(), IS_NOT_WORK_DAY)) {
                // 如果中间日期发现非工作日，需要再次往后推时间
                countNotWorkDay++;
            }
        }
        // 退出循环之后，获取到其中有多少个非工作日，则直接按照原始的自然日 + 非工作日天数 进行计算即可
        int finalCountNotWorkDay = countNotWorkDay;
        // 这里是最终算出来的时间
        int countNotWorkFinalDay = finDay + countNotWorkDay;
        Optional<SaCalendarDayDto> finalCalcDay = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), String.valueOf(countNotWorkFinalDay))).findFirst();
        if (!finalCalcDay.isPresent()) {
            String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 %s 日数据，请检查日历表数据是否正确， finalCalcDay == null", bankSettleCycle);
            log.warn(logError);
            throw new RuntimeException(logError);
        }
        if (log.isDebugEnabled()) {
            String debugLog = String.format("非工作日天数为: 【%s】，所以最终计算 【%s】 换算为  【D+%s】，最后获取日历结果为 %s", finalCountNotWorkDay, bankSettleCycle, countNotWorkFinalDay, JSON.toJSONString(finalCalcDay.get()));
            log.debug(debugLog);
        }
        // 如果不是工作日，还需再往后推
        if(Objects.equals(finalCalcDay.get().getIsWorkDay(), IS_WORK_DAY)){
            return finalCalcDay.get().getCalendarDate();
        }
        int limitSize = CollectionUtils.size(calendarDayDtos);
        for (int extDay = 0; extDay < limitSize; extDay++) {
            // 一天天往后或者往前推，直到找到可用数据为止
            int finalPostpone = extDay;
            Optional<SaCalendarDayDto> findVal = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), String.format("%s", countNotWorkFinalDay + finalPostpone))).findFirst();
            if(!findVal.isPresent()){
                continue;
            }
            if (Objects.equals(findVal.get().getIsWorkDay(), IS_NOT_WORK_DAY) && log.isDebugEnabled()) {
                String debugLog = String.format("由于【%s】不为正确日期，推送天数 %s 天，再次判断当日期:%s ", findVal.get().getCalendarDate(), extDay+1, JSON.toJSONString(findVal.get()));
                log.debug(debugLog);
            }else{
                if (log.isDebugEnabled()) {
                    String debugLog = String.format("非工作日天数为: 【%s】，所以最终计算 【%s】 换算为 【D+%s】，最后获取日历结果为 %s", finalCountNotWorkDay+extDay, bankSettleCycle, countNotWorkFinalDay + finalPostpone, JSON.toJSONString(findVal.get()));
                    log.debug(debugLog);
                }
                log.info("获取下一个工作日或者自然日出参：{}", JSON.toJSONString(findVal));
                return findVal.get().getCalendarDate();
            }
        }
        String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 %s 日数据，请检查日历表数据是否正确， finalCalcDay == null", bankSettleCycle);
        log.warn(logError);
        throw new RuntimeException(logError);
    }

    /**
     * 获取T-N日工作日数据问题
     *
     * @param calendarDataProcessBo
     * @return java.lang.String
     * @description 获取T-N日工作日数据问题
     * @author 赵小东
     * @date 2022/6/16 18:38
     */
    public String findPrevDayByCalendarList(CalendarDataProcessBo calendarDataProcessBo) throws IllegalAccessException {
        Objects.requireNonNull(calendarDataProcessBo);
        log.info("获取上一个工作日或者自然日入参：结算周期:{}, 需要额外推迟的工作日天数:{}", calendarDataProcessBo.getBankSettleCycle(), calendarDataProcessBo.getExtDayOfWorkDayCount());
//        if (!StringUtils.isAllBlank(calendarDataProcessBo.getBankSettleCycle())
//                || CollectionUtil.isEmpty(calendarDataProcessBo.getCalendarDayDtos())) {
//            throw new IllegalArgumentException("传递参数有误，请确保所有参数均已传递");
//        }
        String bankSettleCycle = calendarDataProcessBo.getBankSettleCycle();
        List<SaCalendarDayDto> calendarDayDtos = calendarDataProcessBo.getCalendarDayDtos();
        // 校验是否为工作日或者自然日
        boolean matches = PREV_TD_DAY_PATTERN.matcher(bankSettleCycle).matches();
        if (!matches) {
            log.error("由于正则表达式{}不符合校验规则{}所以对账定时任务无法处理时间", bankSettleCycle, PREV_TD_DAY_PATTERN);
            throw new UnsupportedOperationException(String.format("由于正则表达式%s不符合校验规则%s所以对账定时任务无法处理时间", bankSettleCycle, PREV_TD_DAY_PATTERN));
        }
        String[] cycDay = bankSettleCycle.split("-");
        // 获取是工作日还是自然日计算
        String tOrDday = cycDay[0];
        // T-N或者D-N的N天
        String addDay = cycDay[1];
        boolean matchWorkDayEnable;
        if (Objects.equals(tOrDday, WORK_DAY_CONFIG_T)) {
            matchWorkDayEnable = true;
        } else if (Objects.equals(tOrDday, WORK_DAY_CONFIG_D)) {
            matchWorkDayEnable = false;
        } else {
            throw new RuntimeException("无法处理【T-N】或者【D-N】以外的数据");
        }
        // 如果天数为 0 ，则直接返回当天
        if (Objects.equals(MATCE_NOW_DAY_LIST, addDay)) {
            String nowByNew = DateHelper.getNowByNew(DateHelper.yyyyMMdd);
            log.info("获取上一个工作日或者自然日出参：{}", nowByNew);
            return nowByNew;
        }
        int finDay = Integer.parseInt(addDay) + calendarDataProcessBo.getExtDayOfWorkDayCount();
        // 如果不需要获取工作日，则直接按照t-N直接取n的数值，但是如果是工作日，则需要不断往后推一天获取到工作日
        // 如果是 D ，直接+N 取内容即可
        if (!matchWorkDayEnable) {
            Optional<SaCalendarDayDto> first = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), Objects.equals(finDay, 0) ? MATCE_NOW_DAY_LIST : String.valueOf(String.format("-%s", finDay)))).findFirst();
            if (!first.isPresent()) {
                throw new UnsupportedOperationException(String.format("calendarDayDtos 未发现 %s 自然日数据", bankSettleCycle));
            }
            return first.get().getCalendarDate();
        }
        // 如果是 T，需要反向计算前面有多少个工作日
        // 计算从 0 到 +N 中间有几个非工作日
        int countNotWorkDay = 0;
        // 循环累加到指定天数
        for (int currDay = 0; currDay <= finDay + 1; currDay++) {
            int finalCurrDay = currDay;
            Optional<SaCalendarDayDto> first = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), Objects.equals(finalCurrDay, 0) ? MATCE_NOW_DAY_LIST : String.format("-%s", finalCurrDay))).findFirst();
            if (!first.isPresent()) {
                String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 T-%s 日数据，请检查日历表数据是否正确", currDay);
                log.warn(logError);
                throw new RuntimeException(logError);
            }
            if (log.isDebugEnabled()) {
                log.debug("获取 T-{} 日数据，当前日期为：{}，是否为工作日：{}", currDay, first.get().getCalendarDate(), first.get().getIsWorkDay());
            }
            if (Objects.equals(first.get().getIsWorkDay(), IS_NOT_WORK_DAY)) {
                // 如果中间日期发现非工作日，需要再次往后推时间
                countNotWorkDay++;
            }
        }
        // 退出循环之后，获取到其中有多少个非工作日，则直接按照原始的自然日 + 非工作日天数 进行计算即可
        int finalCountNotWorkDay = countNotWorkDay;
        // 这里是最终算出来的时间
        int countNotWorkFinalDay = finDay + countNotWorkDay;
        Optional<SaCalendarDayDto> finalCalcDay = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), Objects.equals(countNotWorkFinalDay, 0) ? MATCE_NOW_DAY_LIST : String.format("-%s", countNotWorkFinalDay))).findFirst();
        if (!finalCalcDay.isPresent()) {
            String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 %s 日数据，请检查日历表数据是否正确， finalCalcDay == null", bankSettleCycle);
            log.warn(logError);
            throw new RuntimeException(logError);
        }
        if (log.isDebugEnabled()) {
            String debugLog = String.format("非工作日天数为: 【%s】，所以最终计算 【%s】 换算为 【D-%s】，最后获取日历结果为 %s", finalCountNotWorkDay, bankSettleCycle, countNotWorkFinalDay, JSON.toJSONString(finalCalcDay.get()));
            log.debug(debugLog);
        }
        // 如果发现计算的结果落在非工作日，还需额外再往前推知道为工作日才能结束，使用日历表数据对半处理
        if (Objects.equals(IS_WORK_DAY, finalCalcDay.get().getIsWorkDay())) {
            return finalCalcDay.get().getCalendarDate();
        }
        // 取日历表半边的数据做处理
        int limitSize = CollectionUtils.size(calendarDayDtos);
        for (int postpone = 0; postpone <= limitSize; postpone++) {
            // 一天天往后或者往前推，直到找到可用数据为止
            int finalPostpone = postpone;
            Optional<SaCalendarDayDto> findVal = calendarDayDtos.stream().filter(item -> Objects.equals(item.getAddDay(), String.format("-%s", countNotWorkFinalDay + finalPostpone))).findFirst();
            if (!findVal.isPresent()) {
                continue;
            }
            if (Objects.equals(findVal.get().getIsWorkDay(), IS_WORK_DAY)) {
                if (log.isDebugEnabled()) {
                    String debugLog = String.format("非工作日天数为: 【%s】，所以最终计算 【%s】 换算为 【D-%s】，最后获取日历结果为 %s", finalCountNotWorkDay + postpone, bankSettleCycle, countNotWorkFinalDay + finalPostpone, JSON.toJSONString(findVal.get()));
                    log.debug(debugLog);
                }
                log.info("获取上一个工作日或者自然日出参：{}", JSON.toJSONString(findVal));
                return findVal.get().getCalendarDate();
            } else {
                if (log.isDebugEnabled()) {
                    String debugLog = String.format("由于【%s】不为正确日期，推送天数 %s 天，再次判断当日期:%s ", findVal.get().getCalendarDate(), postpone + 1, JSON.toJSONString(findVal.get()));
                    log.debug(debugLog);
                }
            }
        }
        String logError = String.format("工作日数据获取，当前工作日列表 calendarDayDtos 无 %s 日数据，请检查日历表数据是否正确， finalCalcDay == null", bankSettleCycle);
        log.warn(logError);
        throw new RuntimeException(logError);
    }


    /**
     * 获取日历表数据，传入指定 yyyy-MM-dd 格式时间获取日历数据
     * @description 获取日历表数据，传入指定 yyyy-MM-dd 格式时间获取日历数据
     * @param tagergetDay
     * @return java.util.List<com.zxd.interview.workdayfind.dto.SaCalendarDayDto>
     * @author 赵小东
     * @date 2022/6/17 22:25
     */
    public List<SaCalendarDayDto> queryCalendarList(String tagergetDay) {
        return calendarTableMapper.queryCalendarList(tagergetDay);
    }


}
